﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FancyBattles.GameScreen;
using FancyBattles.GameScreen.StateMachine;

namespace FancyBattles.GameEntities
{
    abstract class BouncingBitch
    {
        #region Atributes
       
        public enum animationState
        {
            Idle,
            Selected,
            Confirmed,
            Action,
            Hit,
            Victory,
            Death,
            Stun,
            Heal
        };

        public const float BITCH_RADIUS = 30;
        private static List<BouncingBitch> bitchesList = new List<BouncingBitch>();
        private static LinkedList<BouncingBitch> firstPlayerBitches = new LinkedList<BouncingBitch>();
        private static LinkedList<BouncingBitch> secondPlayerBitches = new LinkedList<BouncingBitch>();
        public static int firstPlayerBitchesCount;
        public static int secondPlayerBitchesCount;
        private static SpriteBatch spriteBatch;
        private static SpriteFont font;
        private Vector2 position;
        private Vector2 speed = Vector2.Zero;
        private float acceleration = 0;
        private int health = 100;
        private BoundingSphere boundingBox;
        private PlayerIndex player;
        private Boolean isSelected = false;
        private Boolean isConfirmed = false;
        private Boolean isDead = false;
        private Boolean isStunned = false;
        private Boolean stillColliding = false;
        private Texture2D texture;
        private Rectangle healthBarSize;
        private int healthBarWidth;
        public static Texture2D blueHealthBar;
        public static Texture2D blueHealthBarFill;
        public static Texture2D redHealthBar;
        public static Texture2D redHealthBarFill;
        public static Texture2D blueSelector;
        public static Texture2D redSelector;

        public static List<Texture2D> separateArrowsBlue;
        public static List<Texture2D> separateArrowsRed;

        public float directionArrowAngle;

        public int arrowDirectionFrame;

        #endregion
        
        #region Properties

        public Vector2 Position
        {
            get { return position; }
            internal set { position = value; }
        }

        public Vector2 Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        public float Acceleration
        {
            internal get { return acceleration; }
            set { acceleration = value; }
        }

        public int Health
        {
            internal get { return health; }
            set { health = value; }
        }

        public PlayerIndex Player
        {
            get { return player; }
            internal set { player = value; }
        }

        public static List<BouncingBitch> BitchesList
        {
            get { return bitchesList; }
        }

        public static LinkedList<BouncingBitch> FirstBitchesList
        {
            get { return firstPlayerBitches; }
        }

        public static LinkedList<BouncingBitch> SecondBitchesList
        {
            get { return secondPlayerBitches; }
        }

        public static SpriteBatch SpriteBatch
        {
            protected get { return spriteBatch; }
            set { spriteBatch = value; }
        }

        public static SpriteFont Font
        {
            protected get { return font; }
            set { font = value; }
        }

        public Boolean IsSelected
        {
            get { return isSelected; }
            set { isSelected = value; }
        }

        public Boolean IsConfirmed
        {
            get { return isConfirmed; }
            set { isConfirmed = value; }
        }

        public Boolean IsDead
        {
            get { return isDead; }
        }

        public Boolean IsStunned
        {
            get { return isStunned; }
        }

        public Texture2D Texture
        {
            get { return texture; }
            internal set { texture = value; }
        }

        public Texture2D HealthBar
        {
            get { return blueHealthBar; }
            internal set { blueHealthBar = value; }
        }

        #endregion

        #region Creators

        public BouncingBitch(Vector2 position, PlayerIndex player)
        {
            this.position = position;
            this.boundingBox = new BoundingSphere(new Vector3(position, 0), BITCH_RADIUS);
            this.healthBarWidth = 64;
            this.healthBarSize = new Rectangle((int)position.X - 17, (int)position.Y + 63, healthBarWidth, 10);

            this.player = player;
            bitchesList.Add(this);
            if (player == PlayerIndex.One)
            {
                firstPlayerBitches.AddLast(this);
            }
            if (player == PlayerIndex.Two)
            {
                secondPlayerBitches.AddLast(this);
            }
            firstPlayerBitchesCount = firstPlayerBitches.Count();
            secondPlayerBitchesCount = secondPlayerBitches.Count();
        }

        #endregion

        #region Methods

        public void Die()
        {
            bitchesList.Remove(this);
            if (this.player.Equals(PlayerIndex.One))
            {
                firstPlayerBitches.Remove(this);
            }
            else
            {
                secondPlayerBitches.Remove(this);
            }
            this.ChangeTexture(animationState.Death);
        }        

        protected virtual void UpdateKinect()
        {
            if (boundingBox.Intersects(Game1.SCREEN_UPPER_BOUND).Equals(PlaneIntersectionType.Intersecting) || boundingBox.Intersects(Game1.SCREEN_LOWER_BOUND).Equals(PlaneIntersectionType.Intersecting))
            {
                speed.Y = -speed.Y;
            }
            if (boundingBox.Intersects(Game1.SCREEN_LEFT_BOUND).Equals(PlaneIntersectionType.Intersecting) || boundingBox.Intersects(Game1.SCREEN_RIGHT_BOUND).Equals(PlaneIntersectionType.Intersecting))
            {
                speed.X = -speed.X;
            }

            speed += speed * acceleration;
            
            if (speed.LengthSquared() < 1.0f)
            {
                speed = Vector2.Zero;
            }

            if (isStunned)
            {
                position += speed;
            }
            else
            {
                position += speed * 0.7f;
            }
            boundingBox.Center = new Vector3(position, 0);
        }

        public virtual void Update()
        {
            UpdateKinect();

            //update Health Bar
            healthBarSize.X = (int)position.X - 17;
            healthBarSize.Y = (int)position.Y + 63;
            healthBarSize.Width = health * healthBarWidth / 100;

            //update auxiliar count variables
            firstPlayerBitchesCount = firstPlayerBitches.Count();
            secondPlayerBitchesCount = secondPlayerBitches.Count();
        }

        public static void UpdateBitches()
        {
            for (int index = 0; index < bitchesList.Count; index++)
            {
                Boolean collided = false;
                for (int index2 = index + 1; index2 < bitchesList.Count; index2++)
                {
                    if (bitchesList[index].boundingBox.Intersects(bitchesList[index2].boundingBox))
                    {
                        collided = true;
                        if (!bitchesList[index].stillColliding)
                        {
                            bitchesList[index].ChangeTexture(animationState.Hit);
                            bitchesList[index2].ChangeTexture(animationState.Hit);

                            //subtrair pontos e achar eixo de colisãoX
                            Vector2 hitAxisX = new Vector2(bitchesList[index2].boundingBox.Center.X - bitchesList[index].boundingBox.Center.X, bitchesList[index2].boundingBox.Center.Y - bitchesList[index].boundingBox.Center.Y);
                            hitAxisX.Normalize();

                            //tirar produto vetorial para encontrar eixo de colisãoY
                            Vector2 hitAxisY = new Vector2(hitAxisX.Y, -hitAxisX.X);
                            hitAxisY.Normalize();

                            //determinante da matriz da inversão
                            float det = 1.0f / (hitAxisX.X * hitAxisY.Y - hitAxisX.Y * hitAxisY.X);

                            Vector2 speedEntity1 = bitchesList[index].speed;
                            Vector2 speedEntity2 = bitchesList[index2].speed;
                            float inertia = speedEntity1.Length() + speedEntity2.Length();

                            //mudança de base de speed1 usando matriz de mudança de base
                            //colocar speed1 na base dos eixos de colisão
                            Vector2 speedModified1 = new Vector2(speedEntity1.X * hitAxisX.X + speedEntity1.Y * hitAxisX.Y, speedEntity1.X * hitAxisY.X + speedEntity1.Y * hitAxisY.Y);

                            //refletir a colisão no eixo de colisão x
                            speedModified1.X = -speedModified1.X;

                            //voltar speed1 para a base normal
                            speedEntity1 = new Vector2(speedModified1.X * hitAxisY.Y - speedModified1.Y * hitAxisX.Y, speedModified1.Y * hitAxisX.X - speedModified1.X * hitAxisY.X);
                            speedEntity1 /= det;


                            //inverter o eixo de colisão x
                            hitAxisX = -hitAxisX;
                            det = 1.0f / (hitAxisX.X * hitAxisY.Y - hitAxisX.Y * hitAxisY.X);

                            //mudança de base de speed2 usando matriz de mudança de base
                            //colocar speed2 na base dos eixos de colisão
                            Vector2 speedModified2 = new Vector2(speedEntity2.X * hitAxisX.X + speedEntity2.Y * hitAxisX.Y, speedEntity2.X * hitAxisY.X + speedEntity2.Y * hitAxisY.Y);

                            //refletir a colisão no eixo de colisão x
                            speedModified2.X = -speedModified2.X;

                            //voltar speed2 para a base normal
                            speedEntity2 = new Vector2(speedModified2.X * hitAxisY.Y - speedModified2.Y * hitAxisX.Y, speedModified2.Y * hitAxisX.X - speedModified2.X * hitAxisY.X);
                            speedEntity2 /= det;

                            if (speedEntity1.Equals(Vector2.Zero))
                            {
                                speedEntity1 = hitAxisX;
                            }
                            if (speedEntity2.Equals(Vector2.Zero))
                            {
                                speedEntity2 = -hitAxisX;
                            }



                            Vector2 a = new Vector2(speedEntity1.X, speedEntity1.Y);
                            Vector2 b = new Vector2(speedEntity2.X, speedEntity2.Y);
                            a.Normalize();
                            b.Normalize();
                            float angle = Vector2.Dot(a, b);
                            if (angle > 0.9f)
                            {
                                if (speedEntity1.Length() > speedEntity2.Length())
                                {
                                    speedEntity1 *= -1;
                                }
                                else
                                {
                                    speedEntity2 *= -1;
                                }
                            }

                            //kinda of magic: using magic numbers 2/3 3/5 ...
                            speedEntity1.Normalize();
                            speedEntity2.Normalize();
                            speedEntity1 *= (3 * inertia - bitchesList[index].speed.Length()) / 5;
                            speedEntity2 *= (3 * inertia - bitchesList[index2].speed.Length()) / 5;

                            //damage
                            if (!bitchesList[index].player.Equals(bitchesList[index2].player))
                            {
                                bitchesList[index].health -= 5 * (int)bitchesList[index2].speed.Length();
                                bitchesList[index2].health -= 5 * (int)bitchesList[index].speed.Length();

                                if (bitchesList[index].GetType().Equals(typeof(Healer)))
                                {
                                    bitchesList[index2].ChangeTexture(animationState.Stun);
                                    bitchesList[index2].isStunned = true;
                                }
                                if (bitchesList[index2].GetType().Equals(typeof(Healer)))
                                {
                                    bitchesList[index].ChangeTexture(animationState.Stun);
                                    bitchesList[index].isStunned = true;
                                }
                            }
                            else
                            {
                                if(bitchesList[index].GetType().Equals(typeof(Healer)))
                                {
                                    if (bitchesList[index2].health + 30 > 100)
                                    {
                                        bitchesList[index2].health = 100;
                                    }
                                    else
                                    {
                                        bitchesList[index2].health += 30;
                                    }
                                    bitchesList[index2].ChangeTexture(animationState.Heal);
                                }
                                if(bitchesList[index2].GetType().Equals(typeof(Healer)))
                                {
                                    if (bitchesList[index].health + 30 > 100)
                                    {
                                        bitchesList[index].health = 100;
                                    }
                                    else
                                    {
                                        bitchesList[index].health += 30;
                                    }
                                    bitchesList[index].ChangeTexture(animationState.Heal);
                                }
                            }

                            //update speed
                            bitchesList[index].speed = speedEntity1;
                            bitchesList[index2].speed = speedEntity2;
                        }
                    }
                }
                bitchesList[index].stillColliding = collided;
            }


            foreach (BouncingBitch bitch in (from bitches in bitchesList where bitches.isDead == true select bitches).ToList())
            {
                bitch.Die();
            }

            foreach(BouncingBitch bitch in bitchesList)
            {
                foreach (Arrow hit in (from arrow in  Arrow.ArrowList where bitch.boundingBox.Contains(arrow.Position).Equals(ContainmentType.Contains) && !bitch.player.Equals(arrow.Player) select arrow).ToList())
                {
                    bitch.health -= Arrow.ARROW_DAMAGE;
                    Arrow.ArrowList.Remove(hit);
                }

                bitch.Update();
            }
        }

        public abstract void ChangeTexture(animationState a);

        public virtual void Draw()
        {
            if(player.Equals(PlayerIndex.One))
            {
                spriteBatch.Draw(blueHealthBar, new Rectangle((int)position.X - 20, (int)position.Y + 60, 70, 16), Color.White);
                spriteBatch.Draw(blueHealthBarFill, healthBarSize, Color.White);
                if (isSelected)
                {
                    spriteBatch.Draw(blueSelector, new Rectangle((int)position.X - 9, (int)position.Y - 75, 47, 47), Color.White);
                }
            }
            else
            {
                spriteBatch.Draw(redHealthBar, new Rectangle((int)position.X - 20, (int)position.Y + 60, 70, 16), Color.White);
                spriteBatch.Draw(redHealthBarFill, healthBarSize, Color.White);
                if (isSelected)
                {
                    spriteBatch.Draw(redSelector, new Rectangle((int)position.X - 9, (int)position.Y - 75, 47, 47), Color.White);
                }
            }
        }

        public static void DrawBitches()
        {
            foreach(BouncingBitch bitch in BitchesList)
            {
                if (bitch.IsConfirmed)
                {
                    if (bitch.player.Equals(PlayerIndex.One))
                    {
                        SpriteBatch.Draw(separateArrowsBlue.ElementAt<Texture2D>(bitch.arrowDirectionFrame), new Vector2(bitch.Position.X + 15, bitch.Position.Y + 14), null, Color.White, bitch.directionArrowAngle, new Vector2(0, 58), 1, SpriteEffects.None, 0);
                    }
                    else
                    {
                        SpriteBatch.Draw(separateArrowsRed.ElementAt<Texture2D>(bitch.arrowDirectionFrame), new Vector2(bitch.Position.X + 14, bitch.Position.Y + 15), null, Color.White, bitch.directionArrowAngle, new Vector2(0, 58), 1, SpriteEffects.None, 0);
                    }
                }
                bitch.Draw();
            }
        }

        public static void CleanBitches()
        {
            foreach (BouncingBitch bitch in BouncingBitch.BitchesList)
            {
                bitch.isStunned = false;
            }
        }

        #endregion

        public static void DeleteBitches()
        {
            bitchesList.Clear();
            firstPlayerBitches.Clear();
            secondPlayerBitches.Clear();
            firstPlayerBitchesCount = 0;
            secondPlayerBitchesCount = 0;
        }
    }
}
